package com.kuding.oss;

import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.*;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.aliyun.oss.model.*;
import com.kuding.commcon.IdWorker;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.kuding.models.AliyunOssPolicy;
import com.kuding.props.aliyun.AliyunOssProperty;
import org.springframework.web.multipart.MultipartFile;

public class AliyunOssComponent {

    private final Log logger = LogFactory.getLog(getClass());

    @Autowired
    private AliyunOssProperty aliyunOssProperty;

    private final String jsonContentType = "application/json;charset=utf-8";

    public AliyunOssComponent(AliyunOssProperty aliYunOssProperty) {
        this.aliyunOssProperty = aliYunOssProperty;
    }

    public AliyunOssComponent() {
        super();
    }

    /**
     * 获取一个ossclient
     *
     * @return
     */
    public OSS createOss() {
        String accessId = aliyunOssProperty.getAccessKeyId();
        String accessSecret = aliyunOssProperty.getSecret();
        String endPoint = aliyunOssProperty.getEndPoint();
        OSS oss = new OSSClientBuilder().build(endPoint, accessId, accessSecret);
        return oss;
    }

    /**
     * 获取有访问权限的路径地址
     *
     * @param bucketName
     * @param key
     * @param expiration
     * @return
     */
    public String generatePresignedUrl(String bucketName, String key, Duration expiration) {
        OSS oss = createOss();
        long milisec = expiration.toMillis();
        Date date = new Date(System.currentTimeMillis() + milisec);
        URL url = oss.generatePresignedUrl(bucketName, key, date);
        return url.toString();
    }

    public String generatePresignedUrl(String url, Duration expiration) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String pattern = "^https?\\:\\/\\/([^\\.]+)\\.SSS\\/(.+)$".replace("SSS", aliyunOssProperty.getEndPoint());
        Matcher matcher = Pattern.compile(pattern).matcher(url);
        if (matcher.find()) {
            String bucket = matcher.group(1);
            String path = matcher.group(2);
            return generatePresignedUrl(bucket, path, expiration);
        } else
            logger.warn("无法匹配Url");
        return url;
    }

    public AliyunOssPolicy createOssPolicy() {
        return createOssPolicy("");
    }

    /**
     * 创建一个临时策略，用于oss的使用,使用配置中默认的bucket
     *
     * @param dir
     * @return
     */
    public AliyunOssPolicy createOssPolicy(String dir) {
        return createOssPolicy(dir, aliyunOssProperty.getDefaultBucket());
    }

    /**
     * 创建一个临时策略，用于oss的使用,使用指定的bucket，需要保证有权限访问
     *
     * @param dir
     * @param bucket
     * @return
     */
    public AliyunOssPolicy createOssPolicy(String dir, String bucket) {
        dir = dir.trim().endsWith("/") ? dir : dir + "/";
        dir = dir.replaceFirst("^\\/", "");
        bucket = bucket == null ? aliyunOssProperty.getDefaultBucket() : bucket;
        OSS oss = createOss();
        try {
            Duration expire = aliyunOssProperty.getPolicyTimeout();
            String host = aliyunOssProperty.getMainUrl(bucket);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            if (!"/".equals(dir))
                policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
            String policy = oss.generatePostPolicy(new Date(System.currentTimeMillis() + expire.toMillis()),
                    policyConds);
            String signature = oss.calculatePostSignature(policy);
            policy = BinaryUtil.toBase64String(policy.getBytes(Charset.forName("UTF-8")));
            AliyunOssPolicy re = new AliyunOssPolicy(bucket, aliyunOssProperty.getAccessKeyId(), policy,
                    signature, dir, host, expire);
            return re;
        } finally {
            oss.shutdown();
        }
    }

    /**
     * 将json传入oss成为一个json请求
     *
     * @param json
     * @param path
     */
    public String putJsonToOss(String json, String path) {
        return putJsonToOss(json, path, aliyunOssProperty.getDefaultBucket());
    }

    /**
     * 将json传入oss成为一个json请求（指定bucket）
     *
     * @param json
     * @param path
     * @param bucket
     */
    public String putJsonToOss(String json, String path, String bucket) {
        OSS oss = createOss();
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(jsonContentType);
            metadata.setContentEncoding("UTF-8");
            oss.putObject(bucket, path, new ByteArrayInputStream(json.getBytes(Charset.forName("UTF-8"))),
                    metadata);
            return genericUrl(bucket, path);
        } finally {
            oss.shutdown();
        }
    }

    /**
     * 将一个字符串上传到oss
     *
     * @param object
     * @param contentType
     * @param path
     * @return
     */
    public String putStringToOss(String object, ContentType contentType, String path) {
        return putStringToOss(object, contentType, path, aliyunOssProperty.getDefaultBucket());
    }

    /**
     * 将一个字符串上传到oss
     *
     * @param object
     * @param contentType
     * @param path
     * @param bucket
     * @return
     */
    public String putStringToOss(String object, ContentType contentType, String path, String bucket) {
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(contentType.toString());
        metadata.setContentEncoding("UTF-8");
        return putStringToOss(object, path, bucket, metadata);
    }

    public String putStringToOss(String object, String path, String bucket, ObjectMetadata objectMetadata) {
        OSS oss = createOss();
        try {
            oss.putObject(bucket, path, new ByteArrayInputStream(object.getBytes(Charset.forName("UTF-8"))),
                    objectMetadata);
            return genericUrl(bucket, path);
        } finally {
            oss.shutdown();
        }
    }

    public String putStringToOss(String object, String path, ObjectMetadata objectMetadata) {
        OSS oss = createOss();
        String bucket = aliyunOssProperty.getDefaultBucket();
        try {
            oss.putObject(bucket, path, new ByteArrayInputStream(object.getBytes(Charset.forName("UTF-8"))),
                    objectMetadata);
            return genericUrl(bucket, path);
        } finally {
            oss.shutdown();
        }
    }

    /**
     * 将文件上传至oss
     *
     * @param file
     * @param path
     * @return
     */
    public String putFileToOss(File file, String path) {
        return putFileToOss(file, aliyunOssProperty.getDefaultBucket(), path);
    }

    /**
     * 将文件上传至oss
     *
     * @param file
     * @param bucket
     * @param path
     * @return
     */
    public String putFileToOss(File file, String bucket, String path) {
        OSS oss = createOss();
        try {
            String fullFilePath = createFileKey(path, file.getName());
            PutObjectRequest objectRequest = new PutObjectRequest(bucket, fullFilePath, file);
            oss.putObject(objectRequest);
            return genericUrl(bucket, fullFilePath);
        } finally {
            oss.shutdown();
        }
    }


    /**
     * 将文件上传至oss并设置过期时间
     *
     * @param file
     * @param path
     * @param expiration
     * @return
     */
    public String putFileToOss(File file, String path, Duration expiration) {
        return putFileToOss(file, aliyunOssProperty.getDefaultBucket(), path, expiration);
    }

    /**
     * 将文件上传至oss并设置过期时间
     *
     * @param file
     * @param bucket
     * @param path
     * @param expiration
     * @return
     */
    public String putFileToOss(File file, String bucket, String path, Duration expiration) {
        OSS oss = createOss();
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setExpirationTime(new Date(System.currentTimeMillis() + expiration.toMillis()));
            String fullFilePath = createFileKey(path, file.getName());
            oss.putObject(bucket, fullFilePath, file, metadata);
            return genericUrl(bucket, fullFilePath);
        } finally {
            oss.shutdown();
        }
    }

    /**
     * 文件图片base64 上传到 OSS
     * @param base64String
     * @return
     */
    public String putPicBase64ToOss(String path, String base64String) {
        return putPicBase64ToOss(path, aliyunOssProperty.getDefaultBucket(), base64String);
    }

    /**
     * 文件图片base64 上传到 OSS
     * @param path          文件保存地址
     * @param bucket
     * @param base64String  base64数据
     * @return
     */
    public String putPicBase64ToOss(String path, String bucket, String base64String) {
        String saveFileName = IdWorker.generateId() + getPicExtension(base64String);
        //判断是否为base64字符串，不是要处理
        if (!Base64.isBase64(base64String)) {
            String[] base64Strs = base64String.split(",");
            if (base64Strs.length > 1) {
                base64String = base64Strs[1];
            }
        }
        byte[] bytes = Base64.decodeBase64(base64String);

        for (int i = 0; i < bytes.length; ++i) {
            if (bytes[i] < 0) {//调整异常数据
                bytes[i] += 256;
            }
        }
        String imgFilePath = path + saveFileName;        //图片保存路径
        OSS oss = createOss();
        // 创建上传文件的元信息，可以通过文件元信息设置HTTP header(设置了才能通过返回的链接直接访问)。
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType("image/jpg");
        // 上传方式 1
        //ossClient.putObject(bucket, imgFilePath, new ByteArrayInputStream(bytes), metadata);
        //return genericUrl(bucket, imgFilePath);;
        // 上传方式 2
        String fullFilePath = createFileKey(path, saveFileName);
        PutObjectRequest objectRequest = new PutObjectRequest(bucket, fullFilePath, new ByteArrayInputStream(bytes), metadata);
        oss.putObject(objectRequest);
        return genericUrl(bucket, fullFilePath);
    }

    /**
     * 文件上传到 OSS
     * @param file  文件
     * @param path  保存地址
     * @return
     */
    public String putMultipartFileToOss(MultipartFile file, String path) {
        return putMultipartFileToOss(file, aliyunOssProperty.getDefaultBucket(), path);
    }

    /**
     * 文件上传到 OSS
     * @param file      文件
     * @param bucket
     * @param path      保存地址
     * @return
     */
    public String putMultipartFileToOss(MultipartFile file, String bucket, String path) {
        OSS oss = createOss();
        String fullFilePath = "";
        try {
            fullFilePath = createFileKey(path, file.getName());
            PutObjectRequest objectRequest = new PutObjectRequest(bucket, fullFilePath, file.getInputStream());
            oss.putObject(objectRequest);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            oss.shutdown();
        }
        return genericUrl(bucket, fullFilePath);
    }

    /**
     * 文件批量上传到 OSS
     * @param fileArr           文件数组
     * @param path              文件保存地址
     * @param isOriginalName    是否使用原文件名保存
     * @return
     */
    public String[] putMultipartFileBatchToOss(MultipartFile[] fileArr, String path, Boolean isOriginalName) {
        List<MultipartFile> fileList = Arrays.asList(fileArr);
        return putMultipartFileBatchToOss(fileList, aliyunOssProperty.getDefaultBucket(), path, isOriginalName);
    }

    /**
     * 批量上传文件到 OSS
     * @param fileArr        文件数组
     * @param bucket
     * @param path           文件保存地址
     * @param isOriginalName 是否使用原文件名保存
     * @return
     */
    public String[] putMultipartFileBatchToOss(MultipartFile[] fileArr, String bucket, String path, Boolean isOriginalName) {
        List<MultipartFile> fileList = Arrays.asList(fileArr);
        return putMultipartFileBatchToOss(fileList, bucket, path, isOriginalName);
    }

    /**
     * 文件批量上传到 OSS
     * @param fileList          文件数组
     * @param path              文件保存地址
     * @param isOriginalName    是否使用原文件名保存
     * @return
     */
    public String[] putMultipartFileBatchToOss(List<MultipartFile> fileList, String path, Boolean isOriginalName) {
        return putMultipartFileBatchToOss(fileList, aliyunOssProperty.getDefaultBucket(), path, isOriginalName);
    }

    /**
     * 文件批量上传到 OSS
     * @param fileList  文件列表
     * @param bucket
     * @param path      文件保存地址
     * @param isOriginalName    是否使用原文件名保存
     * @return
     */
    public String[] putMultipartFileBatchToOss(List<MultipartFile> fileList, String bucket, String path, Boolean isOriginalName) {
        String[] filePaths = new String[fileList.size()];
        int i = 0;
        OSS oss = createOss();
        ObjectMetadata metadata = new ObjectMetadata();
        for (MultipartFile fileItem : fileList) {
            //图片保存文件名
            String originalFileName = fileItem.getOriginalFilename();
            String ext = originalFileName.substring(originalFileName.lastIndexOf("."));
            String saveFileName = IdWorker.generateId() + ext;
            String fullFilePath = createFileKey(path, (isOriginalName) ? originalFileName : saveFileName);
            InputStream inputStream;
            try {
                inputStream = fileItem.getInputStream();
                metadata.setContentType(getContentType(originalFileName));
                PutObjectRequest objectRequest = new PutObjectRequest(bucket, fullFilePath, inputStream);
                oss.putObject(objectRequest);
                inputStream.close();
                filePaths[i] = genericUrl(bucket, fullFilePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
            i++;
        }
        return filePaths;
    }


    /**
     * 文件上传到 OSS
     * @param file      输入流
     * @param fileName  文件名
     * @param path      文件保存地址
     * @return
     */
    public String putInputStreamToOss(InputStream file, String fileName, String path) {
        return putInputStreamToOss(file, fileName, aliyunOssProperty.getDefaultBucket(), path);
    }

    /**
     * 文件上传到 OSS
     * @param file      文件输入流
     * @param fileName  文件名称
     * @param bucket
     * @param path      文件保存地址
     * @return
     */
    public String putInputStreamToOss(InputStream file, String fileName, String bucket, String path) {
        OSS oss = createOss();
        try {
            String fullFilePath = createFileKey(path, fileName);
            PutObjectRequest objectRequest = new PutObjectRequest(bucket, fullFilePath, file);
            oss.putObject(objectRequest);
            return genericUrl(bucket, fullFilePath);
        } finally {
            oss.shutdown();
        }
    }

    /**
     * 从base64字符串中获取图片后缀
     *
     * @return
     */
    private static String getPicExtension(String base64String) {
        //默认使用jpg
        if (base64String.contains("image/jpg")) {
            return ".jpg";
        } else if (base64String.contains("image/png")) {
            return ".png";
        } else if (base64String.contains("image/gif")) {
            return ".gif";
        } else {
            return ".jpg";
        }
    }

    /**
     * 通过文件名判断并获取OSS服务文件上传时文件的contentType
     */
    private static String getContentType(String fileName) {
        String fileExtension = fileName.substring(fileName.lastIndexOf("."));
        if (".bmp".equalsIgnoreCase(fileExtension))
            return "image/bmp";
        if (".gif".equalsIgnoreCase(fileExtension))
            return "image/gif";
        if (".jpeg".equalsIgnoreCase(fileExtension))
            return "image/jpeg";
        if (".jpg".equalsIgnoreCase(fileExtension) || ".png".equalsIgnoreCase(fileExtension))
            return "image/jpg";
        //if (".png".equalsIgnoreCase(fileExtension))
        //   return "image/png";
        if (".html".equalsIgnoreCase(fileExtension))
            return "text/html";
        if (".txt".equalsIgnoreCase(fileExtension))
            return "text/plain";
        if (".vsd".equalsIgnoreCase(fileExtension))
            return "application/vnd.visio";
        if (".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension))
            return "application/vnd.ms-powerpoint";
        if (".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension))
            return "application/msword";
        if (".xml".equalsIgnoreCase(fileExtension))
            return "text/xml";
        if (".mp4".equalsIgnoreCase(fileExtension))
            return "video/mp4";
        if (".mp3".equalsIgnoreCase(fileExtension))
            return "audio/mp3";
        return "text/html";
    }

    /**
     * 获取文件地址
     * @param bucket
     * @param fullFilePath 文件保存地址
     * @return
     */
    public String genericUrl(String bucket, String fullFilePath) {
        if (fullFilePath.startsWith("/"))
            fullFilePath = fullFilePath.replaceFirst("\\/", "");
        String host = aliyunOssProperty.getMainUrl(bucket);
        String url = String.format("%s%s", host,
                URLEncodedUtils.formatSegments(Arrays.asList(fullFilePath.split("\\/")), Charset.forName("UTF-8")));
        return url;
    }

    /**
     * 获取文件外网地址
     * @param fullFilePath 文件保存地址
     * @return
     */
    public String genericUrl(String fullFilePath) {
        return genericUrl(aliyunOssProperty.getDefaultBucket(), fullFilePath);
    }

    private String createFileKey(String path, String fileName) {
        String fullPath = FilenameUtils.normalize(FilenameUtils.concat(path, fileName), true);
        logger.debug(path + "/" + fileName + "->" + fullPath);
        return fullPath;
    }

    /**
     * 删除一个oss对象
     *
     * @param path
     */
    public void deleteObject(String path) {
        deleteObject(path, aliyunOssProperty.getDefaultBucket());
    }

    /**
     * 删除一个oss对象（指定bucket）
     *
     * @param path
     * @param bucket
     */
    public void deleteObject(String path, String bucket) {
        OSS oss = createOss();
        try {
            oss.deleteObject(bucket, path);
        } finally {
            oss.shutdown();
        }
    }
}
